
#pragma once

#include <tbb/parallel_scan.h>
#include <tbb/task_scheduler_init.h>
#include <tbb/tick_count.h>

#include "Common.h"

using namespace tbb;

//Functor for parallel_scan
template <class T>
struct tbbScanBody
{
	private:
		Matrix2x2<T> sum;
		int first;			// flag to make one less operation for first range
	public:
		//Constructor fills sum with initial matrix
		inline tbbScanBody()
		{
			sum = Matrix2x2<T>(1, 1, 1, 0);
			first = 1;
		}
		//Splitting constructor
		inline tbbScanBody(tbbScanBody &b, tbb::split)
		{
			sum =  Matrix2x2<T>(1, 1, 1, 0);
			first = 1;
		}
		//Returns required fibonacci number.
		inline const T &getFibonacciNumber() const
		{ return sum(0, 0); }
		//Join point
		inline void reverse_join(tbbScanBody &a)
		{ sum = sum * a.sum; }
		//Assign point
		inline void assign(tbbScanBody &b)
		{ sum = b.sum; }
		//Process multiplications. For two tags.
		template<typename WTF>
		void operator()( const blocked_range<int> &r, WTF)
		{
			//See tag.is_final_scan() for what tag is used.
			for(int k = r.begin() + first; k < r.end(); k++)
	            sum = sum * Matrix2x2<T>(1, 1, 1, 0);
			first = 0;		//resetting flag.
		}
};
//Calculates n-th Fibonacci number via tbb::parallel_scan.
//Returns number + elapsed time (in seconds).
template <class WorkType>
std::pair<WorkType, double> tbbFibonacci_Scan(size_t n)
{
    tbbScanBody<WorkType> b;
	tbb::tick_count t_start = tbb::tick_count::now();
    parallel_scan(blocked_range<int>(1, (int)n + 1, 3), b);
	tbb::tick_count t_end = tbb::tick_count::now();
	return std::make_pair(b.getFibonacciNumber(), (t_end - t_start).seconds());
}
